package com.diy.sigmund.diyuser.service.impl;

import com.diy.sigmund.diycommon.entity.CustomUser;
import com.diy.sigmund.diycommon.entity.ResultMsg;
import com.diy.sigmund.diycommon.entity.constant.CommonConstant;
import com.diy.sigmund.diycommon.entity.constant.UserConstant;
import com.diy.sigmund.diycommon.entity.po.user.UserAuth;
import com.diy.sigmund.diycommon.entity.po.user.UserBase;
import com.diy.sigmund.diycommon.middleware.cache.redis.RedisDistributedLock;
import com.diy.sigmund.diycommon.middleware.cache.redis.RedisService;
import com.diy.sigmund.diycommon.spring.ContextHolder;
import com.diy.sigmund.diycommon.spring.CustomApplicationContext;
import com.diy.sigmund.diycommon.util.VerifyParamUtil;
import com.diy.sigmund.diycommon.util.json.JacksonUtil;
import com.diy.sigmund.diycommon.util.seq.SeqUtil;
import com.diy.sigmund.diyuser.common.spring.config.oauth2.BCryptPasswordEncoderUtil;
import com.diy.sigmund.diyuser.entity.dto.UserAuthDTO;
import com.diy.sigmund.diyuser.entity.enumerate.IdentityTypeEnum;
import com.diy.sigmund.diyuser.entity.enumerate.IsExternalLoginEnum;
import com.diy.sigmund.diyuser.entity.enumerate.UserRoleEnum;
import com.diy.sigmund.diyuser.entity.vo.CustomOAuth2AccessToken;
import com.diy.sigmund.diyuser.entity.vo.CustomUserVO;
import com.diy.sigmund.diyuser.entity.vo.UserBaseVO;
import com.diy.sigmund.diyuser.mapper.UserAuthMapper;
import com.diy.sigmund.diyuser.mapper.UserBaseMapper;
import com.diy.sigmund.diyuser.mapper.UserDeviceMapper;
import com.diy.sigmund.diyuser.mapper.UserLocationMapper;
import com.diy.sigmund.diyuser.service.UserAuthService;
import com.diy.sigmund.diyuser.service.oauth2.OAuth2Service;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
 * @author ylm-sigmund
 * @since 2021/8/19 16:57
 */
@Service
public class UserAuthServiceImpl implements UserAuthService {

    @Autowired
    private UserAuthMapper userAuthMapper;
    @Autowired
    private UserBaseMapper userBaseMapper;
    @Autowired
    private UserDeviceMapper userDeviceMapper;
    @Autowired
    private UserLocationMapper userLocationMapper;
    @Autowired
    private OAuth2Service oAuth2Service;
    @Autowired
    private BCryptPasswordEncoderUtil bCryptPasswordEncoderUtil;
    @Autowired
    private RedisService redisService;
    @Autowired
    private RedisDistributedLock redisDistributedLock;

    private static final Logger log = LoggerFactory.getLogger(UserAuthServiceImpl.class);

    @Override
    public List<UserAuth> selectByUserId(String userId) {
        return userAuthMapper.listByUserId(userId);
    }

    @Override
    public ResultMsg register(UserAuthDTO dto) {
        log.info("该方法中存在事务={},getCurrentTransactionName={},getCurrentTransactionIsolationLevel={}",
                TransactionSynchronizationManager.isActualTransactionActive(),
                TransactionSynchronizationManager.getCurrentTransactionName(),
                TransactionSynchronizationManager.getCurrentTransactionIsolationLevel());
        // 校验库中有没有重名的数据
        final ResultMsg repeat = verifyIdentifierRepeat(dto);
        if (Objects.nonNull(repeat)) {
            return repeat;
        }
        // 注册数据入库
        final UserAuth userAuth = buildUserAuthDO(dto);
        // 通过spring管理的bean调用可以使用事务，普通调用无法实现
        ResultMsg insertResult = CustomApplicationContext.getBean(UserAuthServiceImpl.class)
                .insertUserAuthAndUserBase(userAuth);
        if (insertResult.unSuccess()) {
            return insertResult;
        }
        UserBase userBase = (UserBase) insertResult.getData();
        // 调用oauth2TokenPassword取出参
        final CustomOAuth2AccessToken oAuth2AccessToken = oAuth2Service.tokenPassword(dto.getIdentifier(),
                dto.getCredential());
        return buildCustomUserVO(userAuth, userBase, oAuth2AccessToken);
    }

    /**
     * 数据入库，保证在一个事务中
     *
     * @param userAuth UserAuthDTO
     * @return ResultMsg
     */
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, timeout = 30, readOnly = false, rollbackFor = {
            Exception.class, RuntimeException.class})
    public ResultMsg insertUserAuthAndUserBase(UserAuth userAuth) {
        log.info("该方法中存在事务={},getCurrentTransactionName={},getCurrentTransactionIsolationLevel={}",
                TransactionSynchronizationManager.isActualTransactionActive(),
                TransactionSynchronizationManager.getCurrentTransactionName(),
                TransactionSynchronizationManager.getCurrentTransactionIsolationLevel());
        final int userAuthCount = userAuthMapper.insert(userAuth);
        if (userAuthCount < 1) {
            return ResultMsg.fail("服务器内部错误，注册失败");
        }
        // 入库基础信息表，需要给权限
        final UserBase userBase = buildUserBaseDO(userAuth);
        final int userBaseCount = userBaseMapper.insertDefault(userBase);
        if (userBaseCount < 1) {
            return ResultMsg.fail("服务器内部错误，注册失败");
        }
        return ResultMsg.success(userBase);
    }

    private UserBase buildUserBaseDO(UserAuth userAuth) {
        final UserBase userBase = new UserBase();
        userBase.setUserId(userAuth.getUserId());
        userBase.setUserRole(UserRoleEnum.NORMAL.getCode());
        userBase.setRegisterSource(userAuth.getIdentityType());
        userBase.setCreateTime(new Date());
        userBase.setUpdateTime(new Date());
        return userBase;
    }

    @Override
    public ResultMsg login(UserAuthDTO dto) {
        final ResultMsg verifyPhoneAndEmail = verifyPhoneAndEmail(dto);
        if (Objects.nonNull(verifyPhoneAndEmail)) {
            return verifyPhoneAndEmail;
        }
        ResultMsg verifyIdentifierExists = verifyIdentifierExists(dto);
        if (verifyIdentifierExists != null) {
            return verifyIdentifierExists;
        }
        // 查询用户授权信息
        UserAuth userAuth = userAuthMapper.getByIdentifier(dto.getIdentifier());
        log.info("userAuthDO={}", JacksonUtil.toJson(userAuth));
        // 匹配密码
        if (!bCryptPasswordEncoderUtil.matches(dto.getCredential(), userAuth.getCredential())) {
            return ResultMsg.fail("密码错误");
        }
        CustomOAuth2AccessToken oAuth2AccessToken = oAuth2Service.tokenPassword(dto.getIdentifier(),
                dto.getCredential());
        UserBase userBase = userBaseMapper.selectByPrimaryKey(userAuth.getUserId());
        return buildCustomUserVO(userAuth, userBase, oAuth2AccessToken);
    }

    @Override
    public ResultMsg buildCustomUserVO(UserAuth userAuth, UserBase userBase,
            CustomOAuth2AccessToken oAuth2AccessToken) {
        String key = UserConstant.USER_ACCESS_TOKEN + oAuth2AccessToken.getAccessToken();
        CustomUser customUser = buildCustomUser(userAuth, userBase, oAuth2AccessToken);
        redisService.set(key, customUser, CommonConstant.TOKEN_EXPIRE_TIME, TimeUnit.MINUTES);

        UserBaseVO userBaseVO = new UserBaseVO();
        BeanUtils.copyProperties(userBase, userBaseVO);
        CustomUserVO customUserVO = new CustomUserVO(customUser, userBaseVO, null, null);
        return ResultMsg.success(customUserVO);
    }

    private CustomUser buildCustomUser(UserAuth userAuth, UserBase userBase,
            CustomOAuth2AccessToken oAuth2AccessToken) {
        CustomUser customUser = new CustomUser();
        customUser.setUserId(userAuth.getUserId());
        customUser.setIdentityType(userAuth.getIdentityType());
        customUser.setIsExternalLogin(userAuth.getIsExternalLogin());
        customUser.setIdentifier(userAuth.getIdentifier());
        customUser.setUserRole(userBase.getUserRole());
        customUser.setAccessToken(oAuth2AccessToken.getAccessToken());
        customUser.setRefreshToken(oAuth2AccessToken.getRefreshToken());
        return customUser;
    }


    /**
     * 校验唯一标识是否存在
     */
    private ResultMsg verifyIdentifierExists(UserAuthDTO dto) {
        final int count = userAuthMapper.countByIdentifierAndIdentityType(dto);
        if (count < 1) {
            // 站内登录校验
            if (Objects.equals(IdentityTypeEnum.PHONE.getCode(), dto.getIdentityType())) {
                return ResultMsg.fail("手机号不存在");
            }
            if (Objects.equals(IdentityTypeEnum.EMAIL.getCode(), dto.getIdentityType())) {
                return ResultMsg.fail("邮箱不存在");
            }
            if (Objects.equals(IdentityTypeEnum.USER_NAME.getCode(), dto.getIdentityType())) {
                return ResultMsg.fail("用户名不存在");
            }
        }
        return null;
    }

    @Override
    public ResultMsg signOut() {
        CustomUser customUser = ContextHolder.getInstance().getCustomUser();
        String accessToken = customUser.getAccessToken();
        String key = UserConstant.USER_ACCESS_TOKEN + accessToken;
        log.info("key={}", key);
        if (!redisService.del(key)) {
            return ResultMsg.fail("登出失败");
        }
        return ResultMsg.success();
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, timeout = 30, readOnly = false, rollbackFor = {
            Exception.class, RuntimeException.class})
    public ResultMsg logout() {
        ResultMsg resultMsg = this.signOut();
        if (resultMsg.unSuccess()) {
            return resultMsg;
        }
        CustomUser context = ContextHolder.getInstance().getCustomUser();
        userAuthMapper.deleteByIdentityTypeAndIdentifier(context.getIdentityType(), context.getIdentifier());
        userBaseMapper.deleteByPrimaryKey(context.getUserId());
        return ResultMsg.success();
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, timeout = 30, readOnly = false, rollbackFor = {
            Exception.class, RuntimeException.class})
    public ResultMsg resetCredential(UserAuthDTO dto) {
        CustomUser customUser = ContextHolder.getInstance().getCustomUser();
        String userId = customUser.getUserId();
        // 配合Oauth2组件，对密码进行加密处理入库
        String encodeCredential = bCryptPasswordEncoderUtil.encode(dto.getCredential());
        int result = userAuthMapper.resetCredential(userId, encodeCredential);
        if (result < 1) {
            return ResultMsg.fail("密码更新失败");
        }
        String remoteUser = CustomApplicationContext.getHttpServletRequest().getRemoteUser();
        // todo 未处理密码与之前密码相同的场景
        return ResultMsg.success();
    }

    private UserAuth buildUserAuthDO(UserAuthDTO dto) {
        final UserAuth userAuth = new UserAuth();
        userAuth.setId(SeqUtil.snowflakeId());
        userAuth.setUserId(SeqUtil.snowflakeId());
        userAuth.setIdentityType(dto.getIdentityType());
        userAuth.setIsExternalLogin(dto.getIsExternalLogin());
        userAuth.setIdentifier(dto.getIdentifier());
        // 配合Oauth2组件，对密码进行加密处理入库
        final String encodeCredential = bCryptPasswordEncoderUtil.encode(dto.getCredential());
        userAuth.setCredential(encodeCredential);
        userAuth.setCreateTime(new Date());
        userAuth.setUpdateTime(new Date());
        return userAuth;
    }

    /**
     * 校验库中有没有重名的数据
     */
    private ResultMsg verifyIdentifierRepeat(UserAuthDTO dto) {
        final ResultMsg verifyPhoneAndEmail = verifyPhoneAndEmail(dto);
        if (Objects.nonNull(verifyPhoneAndEmail)) {
            return verifyPhoneAndEmail;
        }
        log.info("站内登录校验开始");
        final int count = userAuthMapper.countByIdentifierAndIdentityType(dto);
        if (count > 0) {
            // 站内登录校验
            if (Objects.equals(IdentityTypeEnum.PHONE.getCode(), dto.getIdentityType())) {
                return ResultMsg.fail("手机号已存在");
            }
            if (Objects.equals(IdentityTypeEnum.EMAIL.getCode(), dto.getIdentityType())) {
                return ResultMsg.fail("邮箱已存在");
            }
            if (Objects.equals(IdentityTypeEnum.USER_NAME.getCode(), dto.getIdentityType())) {
                return ResultMsg.fail("用户名已存在");
            }
        }
        return null;
    }

    /**
     * 校验手机号和邮箱格式
     */
    private ResultMsg verifyPhoneAndEmail(UserAuthDTO dto) {
        // 站外登录的校验暂时缺失
        if (Objects.equals(IsExternalLoginEnum.EXTERNAL.getCode(), dto.getIsExternalLogin())) {
            return null;
        }
        if (Objects.equals(IdentityTypeEnum.PHONE.getCode(), dto.getIdentityType())) {
            if (!Pattern.matches(VerifyParamUtil.PHONE_PATTERN, dto.getIdentifier())) {
                return ResultMsg.fail("手机号格式非法");
            }
        }
        if (Objects.equals(IdentityTypeEnum.EMAIL.getCode(), dto.getIdentityType())) {
            if (!Pattern.matches(VerifyParamUtil.EMAIL_PATTERN, dto.getIdentifier())) {
                return ResultMsg.fail("邮箱格式非法");
            }
        }
        return null;
    }
}
